﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class provides static mathematic methods that usually often needed.
    /// </summary>
    [Serializable]
    public static class ExtendedMath
    {
        /// <summary>
        /// Transforms a number to an always positive value.
        /// </summary>
        /// <param name="number">The number to fab.</param>
        /// <returns>The absolute value of a number.</returns>
        public static double Fabs(double number)
        {
            if (number < 0)
            {
                return -number;
            }

            return number;
        }

        /// <summary>
        /// Transforms a number to an always positive value.
        /// </summary>
        /// <param name="number">The complex number to fab.</param>
        /// <returns>The absolute value of the complex number.</returns>
        public static ComplexNumber Fabs(ComplexNumber number)
        {
            if (number == (ComplexNumber)null)
            {
                throw new ArgumentNullException("number");
            }

            ComplexNumber result = new ComplexNumber(number.RealNumberPart, number.ImaginaryNumberPart);

            if (result.RealNumberPart < 0)
            {
                result.InvertRealNumberPart();
            }

            if (result.ImaginaryNumberPart < 0)
            {
                result.InvertImaginaryNumberPart();
            }

            return result;
        }

        /// <summary>
        /// Searches the greatest common divisor of two numbers.
        /// </summary>
        /// <param name="number1">The first number.</param>
        /// <param name="number2">The second number.</param>
        /// <returns>The greatest common divisor.</returns>
        public static long GCD(long number1, long number2)
        {
            long remainder = 0;

            do
            {
                remainder = number1 % number2;
                number1 = number2;
                number2 = remainder;
            } while (remainder != 0);

            return number1;
        }

        /// <summary>
        /// Searches the greatest common divisor of two numbers.
        /// </summary>
        /// <param name="number1">The first number.</param>
        /// <param name="number2">The second number.</param>
        /// <returns>The greatest common divisor.</returns>
        public static ulong GCD(ulong number1, ulong number2)
        {
            ulong remainder = 0;

            do
            {
                remainder = number1 % number2;
                number1 = number2;
                number2 = remainder;
            } while (remainder != 0);

            return number1;
        }

        /// <summary>
        /// Returns the remainder of the specified two numbers.
        /// </summary>
        /// <param name="a">The first number.</param>
        /// <param name="b">The second number.</param>
        /// <returns>Returns the remainder of the specified two numbers.</returns>
        public static int Remainder(int a, int b)
        {
            if (a < b)
            {
                return 0;
            }

            return (a - (a % b)) / b;
        }

        /// <summary>
        /// Computes the modular multiplicative inverse of two numbers. If the inverse does not 
        /// exist, it returns -1.
        /// </summary>
        /// <param name="a">The first number.</param>
        /// <param name="b">The second number</param>
        /// <returns>Returns the modular multiplicative inverse of two numbers. If the inverse does 
        /// not exist, it returns -1.</returns>
        public static int ModularMultiplicativeInverse(int a, int b)
        {
            int x = 0;
            int y = 1;
            int lastx = 1;
            int lasty = 0;

            while (b != 0)
            {
                int tempuri = b;
                int quotient = ExtendedMath.Remainder(a, b);

                b = a % b;
                a = tempuri;
                tempuri = x;
                x = lastx - quotient * x;
                lastx = tempuri;
                tempuri = y;
                y = lasty - quotient * y;
                lasty = tempuri;
            }

            if (a == 1)
            {
                return lastx;
            }

            return -1;
        }

        /// <summary>
        /// Gets the smallest common multiplier if two numbers.
        /// </summary>
        /// <param name="number1">The first number.</param>
        /// <param name="number2">The second number.</param>
        /// <returns>The smallest common multiplier.</returns>
        public static long SCM(long number1, long number2)
        {
            return (number1 * number1) / ExtendedMath.GCD(number1, number2);
        }

        /// <summary>
        /// Computes the n-th root of a number.
        /// </summary>
        /// <param name="number">The number.</param>
        /// <param name="root">The specified root.</param>
        /// <returns>Returns the n-th root of a number.</returns>
        public static double Root(double number, double root)
        {
            return Math.Pow(number, 1 / root);
        }

        /// <summary>
        /// Converts an angle from radian to degree.
        /// </summary>
        /// <param name="rad">The radian value.</param>
        /// <returns>The converted angle.</returns>
        public static double ToDegree(double rad)
        {
            return 180 / Math.PI * rad;
        }

        /// <summary>
        /// Converts an angle from degree to radian.
        /// </summary>
        /// <param name="deg">The degree value.</param>
        /// <returns>The converted angle.</returns>
        public static double ToRadians(double deg)
        {
            return deg / 180 * Math.PI;
        }

        /// <summary>
        /// Solves a quadratic equation. If the discriminant is negative the method will return null.
        /// </summary>
        /// <param name="p">The p value.</param>
        /// <param name="q">The q value.</param>
        /// <returns>The result of a quadratic equation. If the discriminant 
        /// is negative the method will return null</returns>
        public static double[] SolveQuadraticEquation(double p, double q)
        {
            double[] result = new double[2];
            double a = -p / 2;
            double b = Math.Pow(p / 2, 2) - q;

            if (b == 0)
            {
                result[0] = a;
                result[1] = a;
            }
            else if (b > 0)
            {
                result[0] = a + Math.Sqrt(b);
                result[1] = a - Math.Sqrt(b);
            }
            else
            {
                return null;
            }

            return result;
        }

        /// <summary>
        /// Solves a quadratic equation with complex results.
        /// </summary>
        /// <param name="p">The p value.</param>
        /// <param name="q">The q value.</param>
        /// <returns>The result of the quadratic equation in a complex form.</returns>
        public static ComplexNumber[] SolveQuadraticEquationComplex(double p, double q)
        {
            ComplexNumber[] result = new ComplexNumber[2];
            double a = -p / 2;
            double b = Math.Pow(p / 2, 2) - q;

            if (ExtendedMath.QuadraticEquationHasComplexResult(p, q))
            {
                result[0] = ExtendedMath.ComplexSqrt(b);
                result[1] = ExtendedMath.ComplexSqrt(b);
                result[1].InvertImaginaryNumberPart();
                result[0].RealNumberPart = a;
                result[1].RealNumberPart = a;
            }
            else
            {
                double[] tempuri = ExtendedMath.SolveQuadraticEquation(p, q);

                result[0] = new ComplexNumber(tempuri[0]);
                result[1] = new ComplexNumber(tempuri[1]);
            }

            return result;
        }

        /// <summary>
        /// Computes the square root of a negative number.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>The result in a complex number form.</returns>
        public static ComplexNumber ComplexSqrt(double value)
        {
            if (value < 0)
            {
                return new ComplexNumber(0, Math.Sqrt(-value));
            }

            return new ComplexNumber(Math.Sqrt(value));
        }

        /// <summary>
        /// Computes the hypotenuse of an orthogonal triangle (a² + b² = c²).
        /// </summary>
        /// <param name="a">The first cathetus a.</param>
        /// <param name="b">The second cathetus b.</param>
        /// <returns>The hypotenuse of an orthogonal triangle c.</returns>
        public static double Phytagoras(double a, double b)
        {
            return Math.Sqrt(Math.Pow(a, 2) + Math.Pow(b, 2));
        }

        /// <summary>
        /// Computes the hypotenuse of an three dimensional triangle (a² + b² + c²= d²).
        /// </summary>
        /// <param name="a">The first cathetus a.</param>
        /// <param name="b">The second cathetus b.</param>
        /// <param name="c">The third cathetus c.</param>
        /// <returns>The hypotenuse of an three dimensional triangle d.</returns>
        public static double Phytagoras(double a, double b, double c)
        {
            return Math.Sqrt(Math.Pow(a, 2) + Math.Pow(b, 2) + Math.Pow(c, 2));
        }

        /// <summary>
        /// Determines whether the specified value is an integer.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value is integer; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsInteger(double value)
        {
            return Math.Ceiling(value) == 0;
        }

        /// <summary>
        /// Determines whether the specified value is an even number.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value is even; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsEven(int value)
        {
            return value % 2 == 0;
        }

        /// <summary>
        /// Determines whether the specified value is an even number.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value is even; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsEven(uint value)
        {
            return value % 2 == 0;
        }

        /// <summary>
        /// Determines whether the specified value is an even number.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value is even; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsEven(Int16 value)
        {
            return value % 2 == 0;
        }

        /// <summary>
        /// Determines whether the specified value is an even number.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value is even; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsEven(long value)
        {
            return value % 2 == 0;
        }

        /// <summary>
        /// Determines whether the specified value is an odd number.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value is odd; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsOdd(int value)
        {
            return !ExtendedMath.IsEven(value);
        }

        /// <summary>
        /// Determines whether the specified value is an odd number.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value is odd; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsOdd(long value)
        {
            return !ExtendedMath.IsEven(value);
        }

        /// <summary>
        /// Determines whether the specified value is an odd number.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value is odd; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsOdd(uint value)
        {
            return !ExtendedMath.IsEven(value);
        }

        /// <summary>
        /// Determines whether the specified value is an odd number.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value is odd; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsOdd(Int16 value)
        {
            return !ExtendedMath.IsEven(value);
        }

        /// <summary>
        /// Determines whether the specified number is a square number.
        /// </summary>
        /// <param name="number">The number to check.</param>
        /// <returns>
        /// True if the specified number is a square number otherwise, false.
        /// </returns>
        public static bool IsSquareNumber(double number)
        {
            return (Math.Sqrt(number) == Math.Ceiling(Math.Sqrt(number)));
        }

        /// <summary>
        /// The mathematic binomial coefficient operation.
        /// </summary>
        /// <param name="n">The first value (n).</param>
        /// <param name="k">The second value (k).</param>
        /// <returns>The computed value of the binomial coefficient.</returns>
        public static uint BinomialCoefficient(uint n, uint k)
        {
            if (Factorials.Factorial(k) * Factorials.Factorial(n - k) == 0u)
            {
                throw new ArgumentException("complex reason");
            }

            uint factorialN = Factorials.Factorial(n);
            uint factorialK = Factorials.Factorial(k);
            uint factorialY = Factorials.Factorial(n - k);

            return (factorialN / (factorialK * factorialY));
        }

        /// <summary>
        /// Checks if the quadratic equation has a complex result.
        /// </summary>
        /// <param name="p">The p value.</param>
        /// <param name="q">The q value.</param>
        /// <returns>True if the result is complex otherwise, false.</returns>
        public static bool QuadraticEquationHasComplexResult(double p, double q)
        {
            return (Math.Pow(p / 2, 2) - q) < 0;
        }

        /// <summary>
        /// Herons formula to calculate the are of a triangle by specifying the three sides.
        /// </summary>
        /// <param name="a">The first side of the triangle.</param>
        /// <param name="b">The second side of the triangle.</param>
        /// <param name="c">The third side of the triangle.</param>
        /// <returns>The area of the triangle.</returns>
        public static double HeronFormula(double a, double b, double c)
        {
            double s = (a + b + c) / 2;

            return Math.Sqrt(s * (s - a) * (s - b) * (s - c));
        }

        /// <summary>
        /// Calculates the area of a general quadrilateral by using the Brahmagupta formula.
        /// </summary>
        /// <param name="a">The length of the first side a.</param>
        /// <param name="b">The length of the second side b.</param>
        /// <param name="c">The length of the third side c.</param>
        /// <param name="d">The length of the fourth side d.</param>
        /// <param name="angleA">The angle between the sides a and d.</param>
        /// <param name="angleB">The angle between the sides b and c.</param>
        /// <returns>The area of the specified general quadrilateral.</returns>
        public static double BrahmaguptaFormula(double a, double b, double c, double d,
                                                double angleA, double angleB)
        {
            double s = 0.5 * (a + b + c + d);

            return Math.Sqrt((s - a) * (s - b) * (s - c) * (s - d) - a * b * c * d
                                                                     * Math.Pow(Math.Cos(0.5 * (angleA + angleB)), 2));
        }

        /// <summary>
        /// Calculates the area of a general quadrilateral by using the Bretschneider formula.
        /// </summary>
        /// <param name="a">The length of the first side a.</param>
        /// <param name="b">The length of the second side b.</param>
        /// <param name="c">The length of the third side c.</param>
        /// <param name="d">The length of the fourth side d.</param>
        /// <param name="p">The first diagonal length p between a and c.</param>
        /// <param name="q">The first diagonal length q between b and d.</param>
        /// <returns>The area of the specified general quadrilateral.</returns>
        public static double BretschneiderFormula(double a, double b, double c, double d,
                                                  double p, double q)
        {
            return 0.25 * Math.Sqrt(4 * Math.Pow(p, 2) * Math.Pow(q, 2)
                                    - (Math.Pow(Math.Pow(b, 2) + Math.Pow(d, 2) - Math.Pow(a, 2) - Math.Pow(c, 2), 2)));
        }

        /// <summary>
        /// Calculates the distance between two points in a plane space.
        /// </summary>
        /// <param name="a">The first point.</param>
        /// <param name="b">The second point.</param>
        /// <returns>The plane distance between the specified points.</returns>
        public static double CalculateDistance(Point2D a, Point2D b)
        {
            return Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2));
        }

        /// <summary>
        ///Computes the hyperbolic secant at the specified value x.
        /// </summary>
        /// <param name="x">The value for evaluation in radians.</param>
        /// <returns>The result of the function at the specified position x.</returns>
        public static double Sech(double x)
        {
            return 1 / Math.Cosh(x);
        }

        /// <summary>
        /// Computes the hyperbolic cosecant at the specified value x.
        /// </summary>
        /// <param name="x">The value for evaluation in radians.</param>
        /// <returns>The result of the function at the specified position x.</returns>
        public static double Csch(double x)
        {
            return 1 / Math.Sinh(x);
        }

        /// <summary>
        /// Computes the arcussecans at the specified value x.
        /// </summary>
        /// <param name="x">The value for evaluation in radians.</param>
        /// <returns>The result of the function at the specified position x.</returns>
        public static double Asec(double x)
        {
            return Math.Acos(1 / x);
        }

        /// <summary>
        /// Computes the arkuscosecans at the specified value x.
        /// </summary>
        /// <param name="x">The value for evaluation in radians.</param>
        /// <returns>The result of the function at the specified position x.</returns>
        public static double Acsc(double x)
        {
            return Math.Asin(1 / x);
        }

        /// <summary>
        /// Computes the areasinus hyperbolicus at the specified value x.
        /// </summary>
        /// <param name="x">The value for evaluation in radians.</param>
        /// <returns>The result of the function at the specified position x.</returns>
        public static double Asinh(double x)
        {
            return Math.Log(x + Math.Sqrt(Math.Pow(x, 2) + 1));
        }

        /// <summary>
        /// Computes the areacosinus hyperbolicus at the specified value x.
        /// </summary>
        /// <param name="x">The value for evaluation in radians.</param>
        /// <returns>The result of the function at the specified position x.</returns>
        public static double Acosh(double x)
        {
            return Math.Log(x + Math.Sqrt(Math.Pow(x, 2) - 1));
        }

        /// <summary>
        /// Computes the areatangens hyperbolicus at the specified value x. This function is defined
        /// for |x| smaller 1.
        /// </summary>
        /// <param name="x">The value for evaluation in radians.</param>
        /// <returns>The result of the function at the specified position x.</returns>
        public static double Atanh(double x)
        {
            if (x == 0)
            {
                return 0;
            }

            return 0.5 * Math.Log((1 + x) / (1 - x));
        }

        /// <summary>
        /// Computes the areakotangens hyperbolicus at the specified value x. This function is defined
        /// for |x| greater 1.
        /// </summary>
        /// <param name="x">The value for evaluation in radians.</param>
        /// <returns>The result of the function at the specified position x.</returns>
        public static double Acoth(double x)
        {
            if (x == 0)
            {
                return 0;
            }

            return 0.5 * Math.Log((1 + x) / (1 - x));
        }

        /// <summary>
        /// Swaps the two specified values a and b.
        /// </summary>
        /// <param name="a">The first value a.</param>
        /// <param name="b">The second value b.</param>
        public static void Swap(ref int a, ref int b)
        {
            int tempuri = a;

            a = b;
            b = tempuri;
        }

        /// <summary>
        /// Swaps the two specified values a and b.
        /// </summary>
        /// <param name="a">The first value a.</param>
        /// <param name="b">The second value b.</param>
        public static void Swap(ref double a, ref double b)
        {
            double tempuri = a;

            a = b;
            b = tempuri;
        }

        /// <summary>
        /// Swaps the two specified values a and b.
        /// </summary>
        /// <param name="a">The first value a.</param>
        /// <param name="b">The second value b.</param>
        public static void Swap(ref uint a, ref uint b)
        {
            uint tempuri = a;

            a = b;
            b = tempuri;
        }

        /// <summary>
        ///  Swaps the two specified values a and b.
        /// </summary>
        /// <typeparam name="T">The type T of the values to swap.</typeparam>
        /// <param name="a">The first value a.</param>
        /// <param name="b">The second value b.</param>
        public static void Swap<T>(ref T a, ref T b)
        {
            T tempuri = a;

            a = b;
            b = tempuri;
        }

        /// <summary>
        /// Returns e raised to the specified power.
        /// </summary>
        /// <param name="a">A number specifying a power.</param>
        /// <returns>The number e raised to the power a.</returns>
        public static ComplexNumber Exp(ComplexNumber a)
        {
            return new ComplexNumber(Math.Exp(a.RealNumberPart) * Math.Cos(a.ImaginaryNumberPart), Math.Exp(a.RealNumberPart) * Math.Sin(a.ImaginaryNumberPart));
        }

        /// <summary>
        /// Returns the sine of the specified angle.
        /// </summary>
        /// <param name="a">A complex angle, measured in radians.</param>
        /// <returns>The complex sine of a.</returns>
        public static ComplexNumber Sin(ComplexNumber a)
        {
            return (ExtendedMath.Exp(new ComplexNumber(0, 1)) * a) - ExtendedMath.Exp(-new ComplexNumber(0, 1) * a) / (new ComplexNumber(0, 1) * 2);
        }

        /// <summary>
        /// Returns the cosine of the specified angle.
        /// </summary>
        /// <param name="a">A complex angle, measured in radians.</param>
        /// <returns>The complex cosine of a.</returns>
        public static ComplexNumber Cos(ComplexNumber a)
        {
            return 0.5 * (ExtendedMath.Exp(new ComplexNumber(0, 1) * a) + ExtendedMath.Exp(-new ComplexNumber(0, 1) * a));
        }

        /// <summary>
        /// Returns the tangent of the specified angle.
        /// </summary>
        /// <param name="a">A complex angle, measured in radians.</param>
        /// <returns>The complex tangent of a.</returns>
        public static ComplexNumber Tan(ComplexNumber a)
        {
            return ExtendedMath.Sin(a) / ExtendedMath.Cos(a);
        }

        /// <summary>
        /// Returns the cotangent of the specified angle.
        /// </summary>
        /// <param name="a">A complex angle, measured in radians.</param>
        /// <returns>The complex cotangent of a.</returns>
        public static ComplexNumber Cot(ComplexNumber a)
        {
            return ExtendedMath.Cos(a) / ExtendedMath.Sin(a);
        }

        /// <summary>
        /// Returns the logarithm of a specified number in e base.
        /// </summary>
        /// <param name="a">A number whose logarithm is to be found.</param>
        /// <returns>The natural logarithm of the specified complex number.</returns>
        public static ComplexNumber Log(ComplexNumber a)
        {
            return new ComplexNumber(Math.Log(a.Abs), a.Argument);
        }

        /// <summary>
        /// Returns a specified number raised to the specified power.
        /// </summary>
        /// <param name="a">The first argument.</param>
        /// <param name="b">The second argument.</param>
        /// <returns>The number x raised to the power y.</returns>
        public static ComplexNumber Pow(ComplexNumber a, ComplexNumber b)
        {
            return ExtendedMath.Exp(b * ExtendedMath.Log(a));
        }

        /// <summary>
        /// Returns a specified number raised to the specified power.
        /// </summary>
        /// <param name="a">The first argument.</param>
        /// <param name="b">The second argument.</param>
        /// <returns>The number x raised to the power y.</returns>
        public static ComplexNumber Pow(double a, ComplexNumber b)
        {
            return ExtendedMath.Exp(b * Math.Log(a));
        }

        /// <summary>
        /// Returns a specified number raised to the specified power.
        /// </summary>
        /// <param name="a">The first argument.</param>
        /// <param name="b">The second argument.</param>
        /// <returns>The number x raised to the power y.</returns>
        public static ComplexNumber Pow(ComplexNumber a, double b)
        {
            return ExtendedMath.Exp(b * ExtendedMath.Log(a));
        }

        /// <summary>
        /// Returns the square root of a specified number.
        /// </summary>
        /// <param name="a">The specified complex number.</param>
        /// <returns>The square root of the specified complex number.</returns>
        public static ComplexNumber Sqrt(ComplexNumber a)
        {
            return Pow(a, 0.5);
        }

        /// <summary>
        /// Returns the complex hyperbolic sine of the specified angle.
        /// </summary>
        /// <param name="a">An complex angle, measured in radians.</param>
        /// <returns>The complex hyperbolic sine of value.</returns>
        public static ComplexNumber Sinh(ComplexNumber a)
        {
            return (ExtendedMath.Exp(a) - ExtendedMath.Exp(-a)) / 2;
        }

        /// <summary>
        /// Returns the complex hyperbolic cosine of the specified angle.
        /// </summary>
        /// <param name="a">An complex angle, measured in radians.</param>
        /// <returns>The complex hyperbolic cosine of value.</returns>
        public static ComplexNumber Cosh(ComplexNumber a)
        {
            return (ExtendedMath.Exp(a) + ExtendedMath.Exp(-a)) / 2;
        }

        /// <summary>
        /// Returns the complex hyperbolic tangent of the specified angle.
        /// </summary>
        /// <param name="a">An complex angle, measured in radians.</param>
        /// <returns>The complex hyperbolic tangent of value.</returns>
        public static ComplexNumber Tanh(ComplexNumber a)
        {
            return (ExtendedMath.Exp(2 * a) - 1) / (ExtendedMath.Exp(2 * a) + 1);
        }
    }
}